﻿using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using MugenInjection.Infrastructure;
using MugenInjection.Logging.Components;
using MugenInjection.Logging.Interface;
using MugenInjection.Logging.Interface.Components;

namespace MugenInjection.Logging.Infrastructure
{
    /// <summary>
    /// A base implementation of a logger.
    /// </summary>
    public abstract class LoggerBase : ILogger
    {
        #region Fields

        private static readonly MethodBase NotDefinedMethod = typeof(LoggerBase).GetMethod("IsNotDefined",
                                                                                            BindingFlags.NonPublic |
                                                                                            BindingFlags.Static);

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="LoggerBase"/> class.
        /// </summary>
        protected LoggerBase(LoggerFactoryComponent loggerFactory, Type type)
        {
            Validate.ArgumentNotNull(loggerFactory, "loggerFactory");
            Validate.ArgumentNotNull(type, "type");
            LoggerFactory = loggerFactory;
            Type = type;
        }

        #endregion

        #region Property

        protected LoggerFactoryComponent LoggerFactory { get; private set; }

        #endregion

        #region Method

        private static void IsNotDefined()
        {
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private void MutateMessageIfNeed(LogLevel logLevel, ref string message, Exception exception)
        {
            if (!LoggerFactory.IsHaveMutators) return;
#if SIMPLESTACKFRAME
            var context = new DefaultLoggerContext(this, new StackFrame(2).GetMethod() ?? NotDefinedMethod, message, logLevel, null, null);
#else
            var context = new DefaultLoggerContext(this, new StackFrame(2, false).GetMethod() ?? NotDefinedMethod,
                                                   message, logLevel, null, exception);
#endif
            foreach (ILogMutator component in LoggerFactory.LogMutators)
            {
                component.Mutate(context);
            }
            message = context.LogMessage;
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private void MutateMessageIfNeed(LogLevel logLevel, ref string message)
        {
            if (!LoggerFactory.IsHaveMutators) return;
#if SIMPLESTACKFRAME
            var context = new DefaultLoggerContext(this, new StackFrame(2).GetMethod() ?? NotDefinedMethod, message, logLevel, null, null);
#else
            var context = new DefaultLoggerContext(this, new StackFrame(2, false).GetMethod() ?? NotDefinedMethod,
                                                   message, logLevel, null, null);
#endif
            foreach (ILogMutator component in LoggerFactory.LogMutators)
            {
                component.Mutate(context);
            }
            message = context.LogMessage;
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private void MutateMessageIfNeed(LogLevel logLevel, ref string message, Exception exception, ref object[] args)
        {
            if (!LoggerFactory.IsHaveMutators) return;
#if SIMPLESTACKFRAME
            var context = new DefaultLoggerContext(this, new StackFrame(2).GetMethod() ?? NotDefinedMethod, message, logLevel, args,
                                                   exception);
#else
            var context = new DefaultLoggerContext(this, new StackFrame(2, false).GetMethod() ?? NotDefinedMethod,
                                                   message, logLevel, args,
                                                   exception);
#endif
            foreach (ILogMutator component in LoggerFactory.LogMutators)
            {
                component.Mutate(context);
            }
            message = context.LogMessage;
            args = context.Args;
        }

        #endregion

        #region Implementation of ILogger

        /// <summary>
        /// Logs the specified message with Debug severity.
        /// </summary>
        /// <param name="message">The message.</param>
        void ILogger.Debug(string message)
        {
            if (!IsDebugEnabled) return;
            MutateMessageIfNeed(LogLevel.Debug, ref message);
            Debug(message);
        }

        /// <summary>
        /// Logs the specified message with Debug severity.
        /// </summary>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        void ILogger.Debug(string format, params object[] args)
        {
            if (!IsDebugEnabled) return;
            MutateMessageIfNeed(LogLevel.Debug, ref format, null, ref args);
            Debug(format, args);
        }

        /// <summary>
        /// Logs the specified exception with Debug severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="message">The message.</param>
        void ILogger.Debug(Exception exception, string message)
        {
            if (!IsDebugEnabled) return;
            MutateMessageIfNeed(LogLevel.Debug, ref message, exception);
            Debug(exception, message);
        }

        /// <summary>
        /// Logs the specified exception with Debug severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        void ILogger.Debug(Exception exception, string format, params object[] args)
        {
            if (!IsDebugEnabled) return;
            MutateMessageIfNeed(LogLevel.Debug, ref format, exception, ref args);
            Debug(exception, format, args);
        }

        /// <summary>
        /// Logs the specified message with Info severity.
        /// </summary>
        /// <param name="message">The message.</param>
        void ILogger.Info(string message)
        {
            if (!IsInfoEnabled) return;
            MutateMessageIfNeed(LogLevel.Info, ref message);
            Info(message);
        }

        /// <summary>
        /// Logs the specified message with Info severity.
        /// </summary>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        void ILogger.Info(string format, params object[] args)
        {
            if (!IsInfoEnabled) return;
            MutateMessageIfNeed(LogLevel.Info, ref format);
            Info(format, args);
        }

        /// <summary>
        /// Logs the specified exception with Info severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="message">The message.</param>
        void ILogger.Info(Exception exception, string message)
        {
            if (!IsInfoEnabled) return;
            MutateMessageIfNeed(LogLevel.Info, ref message, exception);
            Info(exception, message);
        }

        /// <summary>
        /// Logs the specified exception with Info severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        void ILogger.Info(Exception exception, string format, params object[] args)
        {
            if (!IsInfoEnabled) return;
            MutateMessageIfNeed(LogLevel.Info, ref format, exception, ref args);
            Info(exception, format, args);
        }

        /// <summary>
        /// Logs the specified message with Trace severity.
        /// </summary>
        /// <param name="message">The message.</param>
        void ILogger.Trace(string message)
        {
            if (!IsTraceEnabled) return;
            MutateMessageIfNeed(LogLevel.Trace, ref message);
            Trace(message);
        }

        /// <summary>
        /// Logs the specified message with Trace severity.
        /// </summary>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        void ILogger.Trace(string format, params object[] args)
        {
            if (!IsTraceEnabled) return;
            MutateMessageIfNeed(LogLevel.Trace, ref format, null, ref args);
            Trace(format, args);
        }

        /// <summary>
        /// Logs the specified exception with Trace severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="message">The message.</param>
        void ILogger.Trace(Exception exception, string message)
        {
            if (!IsTraceEnabled) return;
            MutateMessageIfNeed(LogLevel.Trace, ref message, exception);
            Trace(exception, message);
        }

        /// <summary>
        /// Logs the specified exception with Trace severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        void ILogger.Trace(Exception exception, string format, params object[] args)
        {
            if (!IsTraceEnabled) return;
            MutateMessageIfNeed(LogLevel.Trace, ref format, exception, ref args);
            Trace(exception, format, args);
        }

        /// <summary>
        /// Logs the specified message with Warn severity.
        /// </summary>
        /// <param name="message">The message.</param>
        void ILogger.Warn(string message)
        {
            if (!IsWarnEnabled) return;
            MutateMessageIfNeed(LogLevel.Warn, ref message);
            Warn(message);
        }

        /// <summary>
        /// Logs the specified message with Warn severity.
        /// </summary>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        void ILogger.Warn(string format, params object[] args)
        {
            if (!IsWarnEnabled) return;
            MutateMessageIfNeed(LogLevel.Warn, ref format, null, ref args);
            Warn(format, args);
        }

        /// <summary>
        /// Logs the specified exception with Warn severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="message">The message.</param>
        void ILogger.Warn(Exception exception, string message)
        {
            if (!IsWarnEnabled) return;
            MutateMessageIfNeed(LogLevel.Warn, ref message, exception);
            Warn(exception, message);
        }

        /// <summary>
        /// Logs the specified exception with Warn severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        void ILogger.Warn(Exception exception, string format, params object[] args)
        {
            if (!IsWarnEnabled) return;
            MutateMessageIfNeed(LogLevel.Warn, ref format, exception, ref args);
            Warn(exception, format, args);
        }

        /// <summary>
        /// Logs the specified message with Error severity.
        /// </summary>
        /// <param name="message">The message.</param>
        void ILogger.Error(string message)
        {
            if (!IsErrorEnabled) return;
            MutateMessageIfNeed(LogLevel.Error, ref message);
            Error(message);
        }

        /// <summary>
        /// Logs the specified message with Error severity.
        /// </summary>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        void ILogger.Error(string format, params object[] args)
        {
            if (!IsErrorEnabled) return;
            MutateMessageIfNeed(LogLevel.Error, ref format, null, ref args);
            Error(format, args);
        }

        /// <summary>
        /// Logs the specified exception with Error severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="message">The message.</param>
        void ILogger.Error(Exception exception, string message)
        {
            if (!IsErrorEnabled) return;
            MutateMessageIfNeed(LogLevel.Error, ref message, exception);
            Error(exception, message);
        }

        /// <summary>
        /// Logs the specified exception with Error severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        void ILogger.Error(Exception exception, string format, params object[] args)
        {
            if (!IsErrorEnabled) return;
            MutateMessageIfNeed(LogLevel.Error, ref format, exception, ref args);
            Error(exception, format, args);
        }

        /// <summary>
        /// Logs the specified message with Fatal severity.
        /// </summary>
        /// <param name="message">The message.</param>
        void ILogger.Fatal(string message)
        {
            if (!IsFatalEnabled) return;
            MutateMessageIfNeed(LogLevel.Fatal, ref message);
            Fatal(message);
        }

        /// <summary>
        /// Logs the specified message with Fatal severity.
        /// </summary>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        void ILogger.Fatal(string format, params object[] args)
        {
            if (!IsFatalEnabled) return;
            MutateMessageIfNeed(LogLevel.Fatal, ref format, null, ref args);
            Fatal(format, args);
        }

        /// <summary>
        /// Logs the specified exception with Fatal severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="message">The message or format template.</param>
        void ILogger.Fatal(Exception exception, string message)
        {
            if (!IsFatalEnabled) return;
            MutateMessageIfNeed(LogLevel.Fatal, ref message, exception);
            Fatal(exception, message);
        }

        /// <summary>
        /// Logs the specified exception with Fatal severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        void ILogger.Fatal(Exception exception, string format, params object[] args)
        {
            if (!IsFatalEnabled) return;
            MutateMessageIfNeed(LogLevel.Fatal, ref format, exception, ref args);
            Fatal(exception, format, args);
        }

        #endregion

        #region Implementation of ILogger Internal

        /// <summary>
        /// Gets the type associated with the logger.
        /// </summary>
        public Type Type { get; private set; }

        /// <summary>
        /// Gets a value indicating whether messages with Debug severity should be logged.
        /// </summary>
        public abstract bool IsDebugEnabled { get; }

        /// <summary>
        /// Gets a value indicating whether messages with Info severity should be logged.
        /// </summary>
        public abstract bool IsInfoEnabled { get; }

        /// <summary>
        /// Gets a value indicating whether messages with Trace severity should be logged.
        /// </summary>
        public abstract bool IsTraceEnabled { get; }

        /// <summary>
        /// Gets a value indicating whether messages with Warn severity should be logged.
        /// </summary>
        public abstract bool IsWarnEnabled { get; }

        /// <summary>
        /// Gets a value indicating whether messages with Error severity should be logged.
        /// </summary>
        public abstract bool IsErrorEnabled { get; }

        /// <summary>
        /// Gets a value indicating whether messages with Fatal severity should be logged.
        /// </summary>
        public abstract bool IsFatalEnabled { get; }

        /// <summary>
        /// Logs the specified message with Debug severity.
        /// </summary>
        /// <param name="message">The message.</param>
        public abstract void Debug(string message);

        /// <summary>
        /// Logs the specified message with Debug severity.
        /// </summary>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        public abstract void Debug(string format, params object[] args);

        /// <summary>
        /// Logs the specified exception with Debug severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="message">The message.</param>
        public abstract void Debug(Exception exception, string message);

        /// <summary>
        /// Logs the specified exception with Debug severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        public abstract void Debug(Exception exception, string format, params object[] args);

        /// <summary>
        /// Logs the specified message with Info severity.
        /// </summary>
        /// <param name="message">The message.</param>
        public abstract void Info(string message);

        /// <summary>
        /// Logs the specified message with Info severity.
        /// </summary>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        public abstract void Info(string format, params object[] args);

        /// <summary>
        /// Logs the specified exception with Info severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="message">The message.</param>
        public abstract void Info(Exception exception, string message);

        /// <summary>
        /// Logs the specified exception with Info severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        public abstract void Info(Exception exception, string format, params object[] args);

        /// <summary>
        /// Logs the specified message with Trace severity.
        /// </summary>
        /// <param name="message">The message.</param>
        public abstract void Trace(string message);

        /// <summary>
        /// Logs the specified message with Trace severity.
        /// </summary>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        public abstract void Trace(string format, params object[] args);

        /// <summary>
        /// Logs the specified exception with Trace severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="message">The message.</param>
        public abstract void Trace(Exception exception, string message);

        /// <summary>
        /// Logs the specified exception with Trace severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        public abstract void Trace(Exception exception, string format, params object[] args);

        /// <summary>
        /// Logs the specified message with Warn severity.
        /// </summary>
        /// <param name="message">The message.</param>
        public abstract void Warn(string message);

        /// <summary>
        /// Logs the specified message with Warn severity.
        /// </summary>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        public abstract void Warn(string format, params object[] args);

        /// <summary>
        /// Logs the specified exception with Warn severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="message">The message.</param>
        public abstract void Warn(Exception exception, string message);

        /// <summary>
        /// Logs the specified exception with Warn severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        public abstract void Warn(Exception exception, string format, params object[] args);

        /// <summary>
        /// Logs the specified message with Error severity.
        /// </summary>
        /// <param name="message">The message.</param>
        public abstract void Error(string message);

        /// <summary>
        /// Logs the specified message with Error severity.
        /// </summary>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        public abstract void Error(string format, params object[] args);

        /// <summary>
        /// Logs the specified exception with Error severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="message">The message.</param>
        public abstract void Error(Exception exception, string message);

        /// <summary>
        /// Logs the specified exception with Error severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        public abstract void Error(Exception exception, string format, params object[] args);

        /// <summary>
        /// Logs the specified message with Fatal severity.
        /// </summary>
        /// <param name="message">The message.</param>
        public abstract void Fatal(string message);

        /// <summary>
        /// Logs the specified message with Fatal severity.
        /// </summary>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        public abstract void Fatal(string format, params object[] args);

        /// <summary>
        /// Logs the specified exception with Fatal severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="message">The message or format template.</param>
        public abstract void Fatal(Exception exception, string message);

        /// <summary>
        /// Logs the specified exception with Fatal severity.
        /// </summary>
        /// <param name="exception">The exception to log.</param>
        /// <param name="format">The message or format template.</param>
        /// <param name="args">Any arguments required for the format template.</param>
        public abstract void Fatal(Exception exception, string format, params object[] args);

        #endregion
    }
}